package org.summerframework.component.mybatisplus;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.summerframework.core.base.dto.BaseDO;
import org.summerframework.core.base.dto.BaseDTO;
import org.summerframework.core.base.result.DefaultResultCode;
import org.summerframework.core.base.service.BaseService;
import org.summerframework.core.check.Insert;
import org.summerframework.core.check.Update;
import org.summerframework.core.check.ValidationUtils;
import org.summerframework.core.exception.WebException;
import org.summerframework.core.jackson.SelectByIdSerializer;
import org.summerframework.core.page.PageResult;
import org.summerframework.core.page.PaginationHelper;
import org.summerframework.core.page.PaginationQuery;
import org.summerframework.core.util.ClassGenricTypeBeanUtils;
import org.summerframework.core.util.ConvertUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;

import javax.validation.groups.Default;
import java.util.ArrayList;
import java.util.List;

/**
 * @author 石超
 * @version v1.0.0
 */
public abstract class AbstractBasePlusService<DAO extends BaseMapper<D>, T extends BaseDTO, Q extends PaginationQuery, D extends BaseDO> extends ServiceImpl<DAO, D> implements BasePlusQueryService<T, Q> {

    @Autowired(required = false)
    protected DAO baseDAO;

    protected BaseMapper<D> getPlusDAO() {
        return baseDAO;
    }

    public abstract Wrapper<D> getWrapper(Q query);

    @Override
    public Integer selectCountByQuery(Q query) throws WebException {
        return getPlusDAO().selectCount(getWrapper(constructQuery(query)));
    }

    @Override
    public List<T> selectByQuery(Q query) throws WebException {
        if (!query.getIsPagination()) {
            return fromDO2DTO(getPlusDAO().selectList(getWrapper(constructQuery(query))));
        }
        return fromDO2DTO(getPlusDAO().selectPage(new Page<>(query.getPageIndex(), query.getPageSize()), getWrapper(constructQuery(query))).getRecords());
    }

    @Override
    public PageResult<T> selectPageResultByQuery(Q query) throws WebException {
        Page<D> page = new Page<>(query.getPageIndex(), query.getPageSize());
        if (query.getIsPagination()) {
            IPage<D> records = getPlusDAO().selectPage(page, getWrapper(constructQuery(query)));
            return PaginationHelper.fetchPage(records.getTotal(), query, fromDO2DTO(records.getRecords()));
        } else {
            List<T> ts = fromDO2DTO(getPlusDAO().selectList(getWrapper(constructQuery(query))));
            return PaginationHelper.fetchPage(ts.size(), query, ts);
        }
    }

    protected PageResult<T> fetchPage(IPage<D> records, PaginationQuery query) {
        return PaginationPlusHelper.fetchPage(records, this::fromDO2DTO);
    }

    @Override
    public Integer deleteById(Long id) {
        return getPlusDAO().deleteById(id);
    }

    @Override
    public T insert(T baseDTO) {
        // 验证参数
        check(BASE_OP.INSERT, baseDTO);
        D basePO = fromDTO2DO(baseDTO);
        getPlusDAO().insert(basePO);
        baseDTO.setId(basePO.getId());

        return baseDTO;
    }

    @Override
    public T selectById(Long id) {
        return fromDO2DTO(getPlusDAO().selectById(id));
    }

    @Override
    public Integer updateById(T baseDTO) {
        // 验证参数
        check(BASE_OP.UPDATE, baseDTO);

        return getPlusDAO().updateById(fromDTO2DO(baseDTO));
    }

    @Override
    public T selectOne(Q query) throws WebException {
        query.setPageSize(1);
        List<T> ts = selectByQuery(constructQuery(query));
        return org.apache.commons.collections4.CollectionUtils.isNotEmpty(ts) ? ts.get(0) : null;
    }

    /**
     * 根据id判断实体是否存在
     *
     * @param id 实体id
     */
    @Override
    public boolean isExistById(Long id) {
        return getPlusDAO().selectById(id) != null;
    }

    @Override
    public T saveOrUpdate(T record) {
        if (isUpdate(record)) {
            updateById(record);
            return record;
        }
        return insert(record);
    }

    public List<T> selectList(Wrapper<D> wrapper) {
        return fromDO2DTO(baseDAO.selectList(wrapper));
    }

    public PageResult<T> selectPage(IPage<D> page, Wrapper<D> wrapper) {
        return PaginationPlusHelper.fetchPage(baseDAO.selectPage(page, wrapper), this::fromDO2DTO);
    }

    public Integer selectCount(Wrapper<D> wrapper) {
        return baseDAO.selectCount(wrapper);
    }

    public T selectOne(Wrapper<D> wrapper) {
        return fromDO2DTO(baseDAO.selectOne(wrapper));
    }

    public D fromDTO2DO(T baseDTO) throws WebException {
        if (baseDTO == null) {
            return null;
        }

        return ConvertUtils.copyProperties((D) ClassGenricTypeBeanUtils.newInstance(getPlusDAO().getClass(), BaseMapper.class, 0), baseDTO);
    }

    protected final Q constructQuery(Q query) {
        if (query.isConstruct()) {
            return query;
        }

        query.setConstruct(true);
        return doConstructQuery(query);
    }

    protected Q doConstructQuery(Q query) {
        return query;
    }

    @Override
    public boolean check(BASE_OP baseOp, T baseDTO) throws WebException {
        switch (baseOp) {
            case INSERT:
                ValidationUtils.validate(baseDTO, Default.class, Insert.class);
                break;
            case UPDATE:
                ValidationUtils.validate(baseDTO, Default.class, Update.class);

                if (!isUpdate(baseDTO)) {
                    throw new WebException(DefaultResultCode.ILLEGAL_ARGUMENT);
                }
                break;
            case DELETE:
                if (!isUpdate(baseDTO)) {
                    throw new WebException(DefaultResultCode.ILLEGAL_ARGUMENT);
                }
                break;
            default:
                break;
        }

        return true;
    }

    public T fromDO2DTO(BaseDO baseDO) throws WebException {
        return fromDO2DTO(baseDO, (T) ClassGenricTypeBeanUtils.newInstance(this.getClass(), BaseService.class, 0));
    }

    public <DTO extends T> DTO fromDO2DTO(BaseDO baseDO, DTO baseDTO) throws WebException {
        if (baseDO == null) {
            return null;
        }

        return SelectByIdSerializer.serializer(ConvertUtils.copyProperties(baseDTO, baseDO));
    }

    public List<D> fromDTO2DO(List<T> baseDTOs) throws WebException {
        List<D> list = new ArrayList<>();
        if (CollectionUtils.isEmpty(baseDTOs)) {
            return list;
        }
        for (T d : baseDTOs) {
            list.add(fromDTO2DO(d));
        }
        return list;
    }

    public List<T> fromDO2DTO(List<? extends BaseDO> baseDOs) throws WebException {
        List<T> list = new ArrayList<>();

        if (CollectionUtils.isEmpty(baseDOs)) {
            return list;
        }

        for (BaseDO d : baseDOs) {
            list.add(fromDO2DTO(d));
        }

        return list;
    }

    protected boolean isUpdate(T record) {
        return record != null && record.getId() != null;
    }

    @Override
    protected Class<D> currentModelClass() {
        return (Class<D>) ClassGenricTypeBeanUtils.getSuperClassGenricType(getClass(), 3);
    }
}
